﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using nPress.Core.Interfaces;
using NHibernate;
using System.Linq.Expressions;
using NHibernate.Linq;
using nPress.Core.Mapping;

namespace nPress.Core.Repositories
{
    public class NHibernateRepository : IRepository
    {

      #region Private Member Variables

        private static ISessionFactory s_sessionFactory;
        private ISession m_session;
        private ITransaction m_transaction;
        private IInterceptor m_interceptor;
        private bool m_isSessionCreator;
        private Configuration m_cfg = new Configuration();

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the NHibernate session being used for this instance of the Repository.
        /// </summary>
        public ISession Session
        {
            get { return m_session; }
        }

        /// <summary>
        /// Gets a value indicating whether or not this instance of the Repository is the
        /// creator of the NHibernate session.
        /// </summary>
        public bool IsSessionCreator
        {
            get { return m_isSessionCreator; }
        }

        /// <summary>
        /// Gets a value indicating whether or not the NHibernate session is open.
        /// </summary>
        public bool IsOpen
        {
            get { return GetIsOpen(m_session); }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the Repository class.
        /// </summary>
        /// <remarks>This automatically opens an NHibernate session.</remarks>
        public NHibernateRepository(): this((Configuration)null)
        {
            
        }

        /// <summary>
        /// Initializes a new instance of the Repository class.
        /// </summary>
        /// <remarks>This automatically opens an NHibernate session.</remarks>
        public NHibernateRepository(Configuration cfg)
        {
            if (cfg != null)
                m_cfg = cfg;
            s_sessionFactory = FluentNHibernate.Cfg.Fluently.Configure(m_cfg).Mappings(
               m => m.FluentMappings.AddFromAssemblyOf<MapBase<IIdentifiable>>()).BuildSessionFactory();
            m_isSessionCreator = true;
            Open();
        }

        /// <summary>
        /// Initializes a new instance of the Repository class.
        /// </summary>
        /// <param name="session">An existing NHibernate session that will be used
        /// by this instance of the Repository.</param>
        public NHibernateRepository(ISession session)
        {
            if (!GetIsOpen(session))
            {
                throw new InvalidOperationException("A new Repository can only be created with an open session.");
            }

            m_session = session;
        }

        #endregion

        #region Public Methods

        #region Save / Update

       public void SaveOrUpdate(object entity)
        {
            if (!IsOpen)
            {
                throw new InvalidOperationException("Repository must be open before an entity can be saved or updated.");
            }

            if (m_transaction == null)
            {
                throw new InvalidOperationException("Saves or updates must be done within a transaction.");
            }

            m_session.SaveOrUpdate(entity);
        }

        public void Save(object entity)
        {
            if (!IsOpen)
            {
                throw new InvalidOperationException("Repository must be open before an entity can be saved.");
            }

            if (m_transaction == null)
            {
                throw new InvalidOperationException("Saves must be done within a transaction.");
            }

            m_session.Save(entity);
        }

        public void Update(object entity)
        {
            if (!IsOpen)
            {
                throw new InvalidOperationException("Repository must be open before an entity can be updated.");
            }

            if (m_transaction == null)
            {
                throw new InvalidOperationException("Updates must be done within a transaction.");
            }

            m_session.Update(entity);
        }

        public void CreateRelationShip(Object entity, Object child, string propertyName)
        {
            throw new NotImplementedException();
        }

        public void RemoveRelationShip(Object entity, Object child, string propertyName)
        {
             throw new NotImplementedException();
        }


        #endregion

        #region Find

        public T FindOne<T>(object id) where T : class
        {
           return (T)m_session.Get(typeof(T), id);
        }
        
        public T FindOne<T>(Expression<Func<T, bool>> expression) where T : class
        {
            return Find<T>().Where(expression).First();
        }

        public bool TryFindOne<T>(object id, out T entity) where T : class
        {
            try
            {
                entity = FindOne<T>(id);
            }
            catch (Exception)
            {
                entity = null;
                return false;
            }
            return true;
        }

        public bool TryFindOne<T>(Expression<Func<T, bool>> expression, out T entity) where T : class
        {
            try
            {
                entity = FindOne(expression);
            }
            catch (Exception)
            {
                entity = null;
                return false;
            }
            return true;
        }

        public IList<T> FindAll<T>() where T : class
        {
            return Find<T>().ToList();        
        }

       public IList<T> FindAll<T>(Expression<Func<T, bool>> expression) where T : class
        {
            return Find<T>().Where(expression).ToList();        
        }

        public IList<T> FindAll<T>(object[] ids) where T : class
        {
            // Can be implemented if T is IIdentifiable, but make dependance
            throw new NotImplementedException();
        }

       public IQueryable<T> Find<T>() where T : class
        {
            return m_session.Linq<T>();
        }

        public IQueryable<T> Find<T>(Expression<Func<T, bool>> expression) where T : class
        {
             return m_session.Linq<T>().Where(expression);
        }

        #endregion

        #region Delete

        public void Delete(object entity)
            {
            if (!IsOpen)
            {
                throw new InvalidOperationException("Repository must be open before an entity can be deleted.");
            }

            if (m_transaction == null)
            {
                throw new InvalidOperationException("Deletes must be done within a transaction.");
            }

            m_session.Delete(entity);
        }

        #endregion
        
        #region Count

        public int Count<T>() where T : class
        {
            return Find<T>().Count();
        }

        public int Count<T>(Expression<Func<T, bool>> expression) where T : class
        {
            return Find<T>().Where(expression).Count();
        }

        #endregion

        #region Transaction

        public void BeginTransaction()
        {
            if (!m_isSessionCreator)
            {
                throw new InvalidOperationException("A gateway that is sharing the session of another gateway cannot start a transaction on the shared session.");
            }

            if (!IsOpen)
            {
                throw new InvalidOperationException("Repository must be open before a transaction can be started on the gateway.");
            }

            if (m_transaction == null)
            {
                m_transaction = m_session.BeginTransaction();
            }
        }

        public void CommitTransaction()
        {
            if (!m_isSessionCreator)
            {
                throw new InvalidOperationException("A gateway that is sharing the session of another gateway cannot commit a transaction on the shared session.");
            }

            if (!IsOpen)
            {
                throw new InvalidOperationException("Repository must be open before a transaction can be committed.");
            }

            if (m_transaction == null)
            {
                throw new InvalidOperationException("Repository must have an open transaction in order to commit.");
            }

            // Do the commit then kill the transaction
            m_transaction.Commit();
            m_transaction = null;
        }

        public void RollbackTransaction()
        {
            if (!m_isSessionCreator)
            {
                throw new InvalidOperationException("A gateway that is sharing the session of another gateway cannot roll back a transaction on the shared session.");
            }

            if (!IsOpen)
            {
                throw new InvalidOperationException("Repository must be open before a transaction can be rolled back.");
            }

            if (m_transaction != null)
            {
                // Do the rollback then kill the transaction
                m_transaction.Rollback();
                m_transaction = null;
            }
        }

        #endregion

        #region Other

        public bool Open()
        {
            if (!m_isSessionCreator)
            {
                throw new InvalidOperationException("A gateway that is sharing the session of another gateway cannot be opened directly.");
            }

            bool wasOpen = true;

            if (m_session == null || !m_session.IsOpen)
            {
                wasOpen = false;

                if (m_interceptor != null)
                {
                    m_session = s_sessionFactory.OpenSession(m_interceptor);
                }
                else
                {
                    m_session = s_sessionFactory.OpenSession();
                }
            }

            // Session is open, connection could be either open or closed. If connection
            // is closed, open it. Indicate that session was open.
            if (!m_session.IsConnected)
            {
                wasOpen = false;
                m_session.Reconnect();
            }

            return wasOpen;
        }

        public void Close()
        {
            if (!m_isSessionCreator)
            {
                throw new InvalidOperationException("A gateway that is sharing the session of another gateway cannot be closed directly.");
            }

            if (m_session == null)
            {
                return;
            }

            if (m_session.IsConnected)
            {
                m_session.Disconnect();
            }

            if (m_session.IsOpen)
            {
                m_session.Close();
            }
        }

        public virtual void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                Close();

                if (m_transaction != null)
                {
                    m_transaction.Dispose();
                    m_transaction = null;
                }

                if (m_session != null)
                {
                    m_session.Dispose();
                    m_session = null;
                }

                m_interceptor = null;
            }
        }

        public void ClearContext()
        {
            m_session.Clear();
        }

        public T Detach<T>(T entity) where T : class
        {
            m_session.Evict(entity);
            return entity;
        }

       public void InitializeRepository()
       {
           var export = new SchemaExport(m_cfg);
           export.Drop(true,true);
           export.Create(true,true);
       }

        #endregion

        public void Dispose()
        {
            if (m_transaction != null)
                RollbackTransaction();
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Private Methods

        private static bool GetIsOpen(ISession session)
        {
            if (session == null || !session.IsOpen)
            {
                return false;
            }

            // Session is open, connection could be either open or closed.
            // If connection is closed, signal that session is closed.
            if (!session.IsConnected)
            {
                return false;
            }

            // Otherwise
            return true;
        }

       
        #endregion

        #region Destructor

        /// <summary>
        /// Destructor for the Repository class.
        /// </summary>
        ~NHibernateRepository()
        {
            Dispose(false);
        }

        #endregion
    }
}